package top.infopub.httpclient.ssl;


import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import top.infopub.httpclient.base.HttpEnum;


/**
 * Https访问工具类
 * @author Awoke
 * @version 2019-4-26
 * @see HttpsGetUtil
 * @since
 */
public class HttpsGetUtil {

    private static Logger log = LoggerFactory.getLogger(HttpsGetUtil.class);

    /**
     * 发起请求并获取结果
     * @param requestUrl 请求地址
     * @param chartSet 字符集
     * @param mimeTypes enum MimeTypes
     * @param connTimeoutMills 设置连接主机超时，单位：毫秒
     * @param readTimeoutMills 设置从主机读取数据超时，单位：毫秒
     * @return 返回的结果
     */
    public static String doGet(String requestUrl, HttpEnum.CharSet chartSet,
                               HttpEnum.MimeType mimeTypes, int connTimeoutMills,
                               int readTimeoutMills) {
        return doRequest(requestUrl, chartSet, mimeTypes, HttpEnum.Method.GET, connTimeoutMills,
            readTimeoutMills, "");
    }

    /**
     * 发起https请求并获取结果 </br>
     * 默认：UTF-8 GET application/x-www-form-urlencoded 超时时间 4s
     * @param requestUrl 请求地址
     * @param requestMethod enum 请求方式
     * @param attachments 附加提交的数据，可以是单字符串{"json":"value"} 或者 多个参数遵循 A=a&B=b格式
     * @return remoteHttps 返回的结果
     */
    public static String doGet(String requestUrl, String attachments) {
        return doRequest(requestUrl, HttpEnum.CharSet.UTF_8, HttpEnum.MimeType.FORM,
            HttpEnum.Method.GET, 4000, 4000, attachments);
    }

    /**
     * 发起https请求并获取结果 </br>
     * 默认：UTF-8 POST application/x-www-form-urlencoded 超时时间 4s
     * @param requestUrl 请求地址
     * @param requestMethod enum 请求方式
     * @param attachments 附加提交的数据，可以是单字符串{"json":"value"} 或者 多个参数遵循 A=a&B=b格式
     * @return remoteHttps 返回的结果
     */
    public static String doPost(String requestUrl, String attachments) {
        return doRequest(requestUrl, HttpEnum.CharSet.UTF_8, HttpEnum.MimeType.FORM,
            HttpEnum.Method.POST, 4000, 4000, attachments);
    }

    /**
     * 发起https请求并获取结果 </br>
     * 默认：UTF-8 application/x-www-form-urlencoded 超时时间 4s
     * @param requestUrl 请求地址
     * @param requestMethod enum 请求方式
     * @param attachments 附加提交的数据，可以是单字符串{"json":"value"} 或者 多个参数遵循 A=a&B=b格式
     * @return remoteHttps 返回的结果
     */
    public static String doRequest(String requestUrl, HttpEnum.Method requestMethod,
                                   String attachments) {
        return doRequest(requestUrl, HttpEnum.CharSet.UTF_8, HttpEnum.MimeType.FORM,
            requestMethod, 4000, 4000, attachments);
    }

    /**
     * 发起请求并获取结果
     * @param requestUrl 请求地址
     * @param chartSet 字符集
     * @param mimeTypes enum MimeTypes
     * @param method enum 请求方法
     * @param connTimeoutMills 设置连接主机超时，单位：毫秒
     * @param readTimeoutMills 设置从主机读取数据超时，单位：毫秒
     * @param attachments 附加提交的数据，可以是单字符串{"json":"value"} 或者 多个参数遵循 A=a&B=b格式
     * @return 返回的结果
     */
    public static String doRequest(String requestUrl, HttpEnum.CharSet chartSet,
                                   HttpEnum.MimeType mimeTypes, HttpEnum.Method method,
                                   int connTimeoutMills, int readTimeoutMills, String attachments) {
        HttpsURLConnection httpUrlConn = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        OutputStream outputStream = null;
        StringBuilder buffer = new StringBuilder("");
        try {
            //bug fiexd for: javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
            System.setProperty("jsse.enableSNIExtension", "false");

            // 创建SSLContext对象，并使用我们指定的信任管理器初始化
            TrustManager[] tm = {new MyX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());

            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            httpUrlConn = (HttpsURLConnection)url.openConnection();
            httpUrlConn.setSSLSocketFactory(ssf);

            //bug fixed for: java.security.cert.CertificateException: No subject alternative DNS name matching
            httpUrlConn.setHostnameVerifier(new CustomizedHostnameVerifier());
            // 设置content_type
            httpUrlConn.setRequestProperty("Content-Type", mimeTypes.getMimeType());
            httpUrlConn.setConnectTimeout(connTimeoutMills);
            httpUrlConn.setReadTimeout(readTimeoutMills);
            httpUrlConn.setDoOutput(true);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);
            // 设置请求方式
            httpUrlConn.setRequestMethod(method.getMethod());
            httpUrlConn.connect();
            // 当有额外数据需要提交时
            if (!StringUtils.isBlank(attachments)) {
                outputStream = httpUrlConn.getOutputStream();
                // 注意编码格式，防止中文乱码
                outputStream.write(attachments.getBytes(chartSet.getCharset()));
                outputStream.flush();
                outputStream.close();
            }
            // 将返回的输入流转换成字符串
            inputStream = httpUrlConn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, chartSet.getCharset());
            bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
        }
        catch (Exception e) {
            log.error("error ", e);
        }
        finally {
            IOUtils.closeQuietly(bufferedReader);
            IOUtils.closeQuietly(inputStreamReader);
            IOUtils.closeQuietly(inputStream);
            IOUtils.closeQuietly(outputStream);
            if (httpUrlConn != null) {
                httpUrlConn.disconnect();
            }
        }
        return buffer.toString();
    }

}